/*
 *  Hamlib KIT backend - FiFi-SDR Receiver(/Tuner) description
 *  Copyright (c) 2010 by Rolf Meeser
 *
 *  Derived from si570avrusb backend:
 *  Copyright (C) 2004-2010 Stephane Fillod
 *
 *
 *   This library is free software; you can redistribute it and/or
 *   modify it under the terms of the GNU Lesser General Public
 *   License as published by the Free Software Foundation; either
 *   version 2.1 of the License, or (at your option) any later version.
 *
 *   This library is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *   Lesser General Public License for more details.
 *
 *   You should have received a copy of the GNU Lesser General Public
 *   License along with this library; if not, write to the Free Software
 *   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 *
 */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#ifdef HAVE_STDINT_H
#include <stdint.h>
#endif

#include <stdlib.h>
#include <stdio.h>
#include <math.h>
#include "hamlib/rig.h"
#include "token.h"

#include "kit.h"

/*
 * Compile this model only if libusb is available
 */
#if defined(HAVE_LIBUSB) && defined(HAVE_USB_H)

#include <errno.h>
#include <usb.h>



/* Selected request codes of the original AVR USB Si570 firmware */
#define REQUEST_SET_FREQ_BY_VALUE	(0x32)
#define REQUEST_SET_XTALL_FREQ		(0x33)
#define REQUEST_READ_MULTIPLY_LO	(0x39)
#define REQUEST_READ_FREQUENCY		(0x3A)

/* FiFi-SDR specific requests */
#define REQUEST_FIFISDR_READ		(0xAB)
#define REQUEST_FIFISDR_WRITE		(0xAC)


/* USB VID/PID, vendor name (idVendor), product name (idProduct).
 * Use obdev's generic shared VID/PID pair and follow the rules outlined
 * in firmware/usbdrv/USBID-License.txt.
 */
#define USBDEV_SHARED_VID			0x16C0  /* VOTI */
#define USBDEV_SHARED_PID			0x05DC  /* Obdev's free shared PID */
#define FIFISDR_VENDOR_NAME			"www.ov-lennestadt.de"
#define FIFISDR_PRODUCT_NAME		"FiFi-SDR"


/* All level controls */
#define FIFISDR_LEVEL_ALL			(0						\
									| RIG_LEVEL_PREAMP 		\
									| RIG_LEVEL_STRENGTH 	\
									| RIG_LEVEL_AF 			\
									| RIG_LEVEL_AGC 		\
									| RIG_LEVEL_SQL			\
									)



static int fifisdr_init(RIG *rig);
static int fifisdr_cleanup(RIG *rig);
static int fifisdr_open(RIG *rig);
static int fifisdr_set_freq(RIG *rig, vfo_t vfo, freq_t freq);
static int fifisdr_get_freq(RIG *rig, vfo_t vfo, freq_t *freq);
static const char *fifisdr_get_info(RIG *rig);
static int fifisdr_set_mode(RIG *rig, vfo_t vfo, rmode_t mode, pbwidth_t width);
static int fifisdr_get_mode(RIG *rig, vfo_t vfo, rmode_t *mode, pbwidth_t * width);
static int fifisdr_set_level(RIG *rig, vfo_t vfo, setting_t level, value_t val);
static int fifisdr_get_level(RIG *rig, vfo_t vfo, setting_t level, value_t *val);
static int fifisdr_get_ext_level(RIG *rig, vfo_t vfo, token_t token, value_t *val);




/* Private tokens. */
#define TOK_LVL_FMCENTER		TOKEN_BACKEND(1)	/* FM center frequency deviation */


/* Extra levels definitions */
static const struct confparams fifisdr_ext_levels[] = {
	{ TOK_LVL_FMCENTER, "fmcenter", "FM center", "Center frequency deviation of FM signal",
		NULL, RIG_CONF_NUMERIC, { .n = { -kHz(5), kHz(5), Hz(1) } }
	},
	{ RIG_CONF_END, NULL, }
};


/** Private instance data. */
struct fifisdr_priv_instance_data {
	double multiplier;
};



/** FiFi-SDR receiver description. */
const struct rig_caps fifisdr_caps = {
	.rig_model = RIG_MODEL_FIFISDR,
	.model_name = "FiFi-SDR",
	.mfg_name = "FiFi",
	.version = "0.5",
	.copyright = "LGPL",
	.status = RIG_STATUS_BETA,

	.rig_type = RIG_TYPE_RECEIVER,
	.ptt_type = RIG_PTT_NONE,
	.dcd_type = RIG_DCD_NONE,
	.port_type = RIG_PORT_USB,

	.write_delay = 0,
	.post_write_delay = 0,
	.timeout = 500,
	.retry = 0,

	.has_get_func = RIG_FUNC_NONE,
	.has_set_func = RIG_FUNC_NONE,
	.has_get_level = FIFISDR_LEVEL_ALL,
	.has_set_level = RIG_LEVEL_SET(FIFISDR_LEVEL_ALL),
	.has_get_parm = RIG_PARM_NONE,
	.has_set_parm = RIG_PARM_SET(RIG_PARM_NONE),

	.level_gran = {},
	.parm_gran = {},

	.extparms = NULL,
	.extlevels = fifisdr_ext_levels,

	.preamp = { 6, RIG_DBLST_END },
	.attenuator = { RIG_DBLST_END },
	.max_rit = Hz(0),
	.max_xit = Hz(0),
	.max_ifshift = Hz(0),

	.vfo_ops = RIG_OP_NONE,
	.scan_ops = RIG_SCAN_NONE,
	.targetable_vfo = 0,
	.transceive = RIG_TRN_OFF,

	.bank_qty = 0,
	.chan_desc_sz = 0,
	.chan_list = {
		RIG_CHAN_END,
	},

	.rx_range_list1 = {
		{.start = kHz(39.1), .end = MHz(175.0),
		 .modes = RIG_MODE_AM | RIG_MODE_SSB | RIG_MODE_FM,
		 .low_power = -1, .high_power = -1,
		 .vfo = RIG_VFO_A, .ant = RIG_ANT_1},
		RIG_FRNG_END,
	},
	.tx_range_list1 = { RIG_FRNG_END, },
	.rx_range_list2 =  {
		{.start = kHz(39.1), .end = MHz(175.0),
		 .modes = RIG_MODE_AM | RIG_MODE_SSB | RIG_MODE_FM,
		 .low_power = -1, .high_power = -1,
		 .vfo = RIG_VFO_A, .ant = RIG_ANT_1},
		RIG_FRNG_END,
	},
	.tx_range_list2 = { RIG_FRNG_END, },

	.tuning_steps = {
		{RIG_MODE_SSB, Hz(1)},
		{RIG_MODE_SSB, Hz(10)},
		{RIG_MODE_SSB, Hz(50)},
		{RIG_MODE_AM, Hz(10)},
		{RIG_MODE_AM, Hz(50)},
		{RIG_MODE_AM, Hz(100)},
		{RIG_MODE_FM, kHz(0.1)},
		{RIG_MODE_FM, kHz(5)},
		{RIG_MODE_FM, kHz(6.25)},
		{RIG_MODE_FM, kHz(10)},
		{RIG_MODE_FM, kHz(12.5)},
		{RIG_MODE_FM, kHz(20)},
		{RIG_MODE_FM, kHz(25)},
		{RIG_MODE_FM, kHz(100)},
		RIG_TS_END,
	},
	/* mode/filter list, remember: order matters! */
	.filters = {
		{RIG_MODE_SSB, kHz(2.7)},	/* normal */
		{RIG_MODE_SSB, kHz(2.2)},
		{RIG_MODE_SSB, kHz(3.3)},
		{RIG_MODE_AM,  kHz(8.0)},	/* normal */
		{RIG_MODE_AM,  kHz(6.2)},
		{RIG_MODE_AM,  kHz(10.0)},
		{RIG_MODE_FM,  kHz(9.0)},	/* normal */
		{RIG_MODE_FM,  kHz(6.0)},
		{RIG_MODE_FM,  kHz(12.5)},
		RIG_FLT_END,
	},

	.cfgparams = NULL,

	.rig_init = fifisdr_init,
	.rig_cleanup = fifisdr_cleanup,
	.rig_open = fifisdr_open,
	.rig_close = NULL,

	.set_freq = fifisdr_set_freq,
	.get_freq = fifisdr_get_freq,
	.set_mode = fifisdr_set_mode,
	.get_mode = fifisdr_get_mode,

	.set_level = fifisdr_set_level,
	.get_level = fifisdr_get_level,
	.get_ext_level = fifisdr_get_ext_level,

	.get_info = fifisdr_get_info,
};




/** Convert from host endianness to FiFi-SDR little endian. */
static uint32_t fifisdr_tole32 (uint32_t x)
{
	return
		(((((x) /        1ul) % 256ul) <<  0) |
		 ((((x) /      256ul) % 256ul) <<  8) |
		 ((((x) /    65536ul) % 256ul) << 16) |
		 ((((x) / 16777216ul) % 256ul) << 24));
}



/** Convert FiFi-SDR little endian to host endianness. */
static uint32_t fifisdr_fromle32 (uint32_t x)
{
	return
		(((((x) >> 24) & 0xFF) * 16777216ul) +
		 ((((x) >> 16) & 0xFF) *    65536ul) +
		 ((((x) >>  8) & 0xFF) *      256ul) +
		 ((((x) >>  0) & 0xFF) *        1ul));
}



/** USB OUT transfer via vendor device command. */
static int fifisdr_usb_write (RIG *rig,
							  int request, int value, int index,
							  char *bytes, int size)
{
	int ret;
	struct usb_dev_handle *udh = rig->state.rigport.handle;

	ret = usb_control_msg(udh, USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_ENDPOINT_OUT,
			request, value, index,
			bytes, size, rig->state.rigport.timeout);

	if (ret != size) {
		rig_debug (RIG_DEBUG_ERR, "%s: usb_control_msg (%d/%d) failed: %s\n",
					__func__,
				   request, value,
					usb_strerror ());
		return -RIG_EIO;
	}

	return RIG_OK;
}



/** USB IN transfer via vendor device command. */
static int fifisdr_usb_read (RIG *rig,
							 int request, int value, int index,
							 char *bytes, int size)
{
	int ret;
	struct usb_dev_handle *udh = rig->state.rigport.handle;

	ret = usb_control_msg(udh, USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_ENDPOINT_IN,
			request, value, index,
			bytes, size, rig->state.rigport.timeout);

	if (ret != size) {
		rig_debug (RIG_DEBUG_ERR, "%s: usb_control_msg (%d/%d) failed: %s\n",
					__func__,
				   request, value,
					usb_strerror ());
		return -RIG_EIO;
	}

	return RIG_OK;
}



int fifisdr_init (RIG *rig)
{
	hamlib_port_t *rp = &rig->state.rigport;
	struct fifisdr_priv_instance_data *priv;

	priv = (struct fifisdr_priv_instance_data*)calloc(sizeof(struct fifisdr_priv_instance_data), 1);
	if (!priv) {
		/* whoops! memory shortage! */
		return -RIG_ENOMEM;
	}

	priv->multiplier = 4;

	rp->parm.usb.vid = USBDEV_SHARED_VID;
	rp->parm.usb.pid = USBDEV_SHARED_PID;

	/* no usb_set_configuration() and usb_claim_interface() */
	rp->parm.usb.iface = -1;
	rp->parm.usb.conf = 1;
	rp->parm.usb.alt = 0;

	rp->parm.usb.vendor_name = FIFISDR_VENDOR_NAME;
	rp->parm.usb.product = FIFISDR_PRODUCT_NAME;

	rig->state.priv = (void*)priv;

	return RIG_OK;
}



int fifisdr_cleanup (RIG *rig)
{
	if (!rig)
		return -RIG_EINVAL;

	if (rig->state.priv)
		free(rig->state.priv);
	rig->state.priv = NULL;

	return RIG_OK;
}



int fifisdr_open (RIG *rig)
{
	int ret;
	uint32_t multiply;
	struct fifisdr_priv_instance_data *priv;


	priv = (struct fifisdr_priv_instance_data*)rig->state.priv;

	/* The VCO is a multiple of the RX frequency. Typically 4 */
	ret = fifisdr_usb_read(rig, REQUEST_FIFISDR_READ, 0,
						   11, /* Read virtual VCO factor */
						   (char *)&multiply, sizeof(multiply));
	if (ret == RIG_OK) {
		priv->multiplier = fifisdr_fromle32(multiply);
	}

	return RIG_OK;
}



const char * fifisdr_get_info (RIG *rig)
{
	static char buf[64];
	int ret;
	uint32_t svn_version;

	ret = fifisdr_usb_read(rig, REQUEST_FIFISDR_READ, 0, 0, (char *)&svn_version, sizeof(svn_version));
	if (ret != RIG_OK) {
		return NULL;
	}

	snprintf(buf, sizeof(buf), "Firmware version: %d", svn_version);

	return buf;
}



int fifisdr_set_freq (RIG *rig, vfo_t vfo, freq_t freq)
{
	struct fifisdr_priv_instance_data *priv = (struct fifisdr_priv_instance_data *)rig->state.priv;
	int ret;
	double mhz;
	uint32_t freq1121;


	/* Need frequency in 11.21 format */
	mhz = (freq * priv->multiplier) / 1e6;
	freq1121 = fifisdr_tole32(round(mhz * 2097152.0));

	ret = fifisdr_usb_write(rig, REQUEST_SET_FREQ_BY_VALUE, 0, 0,
							(char *)&freq1121, sizeof(freq1121));
	if (ret != RIG_OK) {
		return -RIG_EIO;
	}

	return RIG_OK;
}



int fifisdr_get_freq (RIG *rig, vfo_t vfo, freq_t *freq)
{
	struct fifisdr_priv_instance_data *priv = (struct fifisdr_priv_instance_data *)rig->state.priv;
	int ret;
	uint32_t freq1121;


	ret = fifisdr_usb_read(rig, REQUEST_READ_FREQUENCY, 0, 0, (char *)&freq1121, sizeof(freq1121));

	if (ret == RIG_OK) {
		freq1121 = fifisdr_fromle32(freq1121);
		*freq = MHz(((double)freq1121 / (1ul << 21)) / priv->multiplier);
	}

	return ret;
}



static int fifisdr_set_mode(RIG *rig, vfo_t vfo, rmode_t mode, pbwidth_t width)
{
	int ret;
	uint8_t fifi_mode;
	uint32_t fifi_width;

	/* Translate mode into FiFi-SDR language */
	fifi_mode = 0;
	switch (mode) {
		case RIG_MODE_AM:
			fifi_mode = 2;
			break;
		case RIG_MODE_LSB:
			fifi_mode = 0;
			break;
		case RIG_MODE_USB:
			fifi_mode = 1;
			break;
		case RIG_MODE_FM:
			fifi_mode = 3;
			break;
		default:
			return -RIG_EINVAL;
	}
	ret = fifisdr_usb_write(rig, REQUEST_FIFISDR_WRITE, 0,
							15,	/* Demodulator mode */
							(char *)&fifi_mode, sizeof(fifi_mode));
	if (ret != RIG_OK) {
		return -RIG_EIO;
	}

	/* Set filter width */
	fifi_width = fifisdr_tole32(width);
	ret = fifisdr_usb_write(rig, REQUEST_FIFISDR_WRITE, 0,
							16,	/* Filter width */
							(char *)&fifi_width, sizeof(fifi_width));
	if (ret != RIG_OK) {
		return -RIG_EIO;
	}

	return RIG_OK;
}



static int fifisdr_get_mode(RIG *rig, vfo_t vfo, rmode_t *mode, pbwidth_t * width)
{
	int ret;
	uint8_t fifi_mode;
	uint32_t fifi_width;


	/* Read current mode */
	ret = fifisdr_usb_read(rig, REQUEST_FIFISDR_READ, 0,
						   15,	/* Demodulator mode */
						   (char *)&fifi_mode, sizeof(fifi_mode));

	if (ret != RIG_OK) {
		return -RIG_EIO;
	}

	/* Translate mode coding */
	*mode = RIG_MODE_NONE;
	switch (fifi_mode) {
		case 0:
			*mode = RIG_MODE_LSB;
			break;
		case 1:
			*mode = RIG_MODE_USB;
			break;
		case 2:
			*mode = RIG_MODE_AM;
			break;
		case 3:
			*mode = RIG_MODE_FM;
			break;
	}

	/* Read current filter width */
	ret = fifisdr_usb_read(rig, REQUEST_FIFISDR_READ, 0,
						   16,	/* Filter width */
						   (char *)&fifi_width, sizeof(fifi_width));
	if (ret != RIG_OK) {
		return -RIG_EIO;
	}

	*width = s_Hz(fifisdr_fromle32(fifi_width));

	return RIG_OK;
}



static int fifisdr_set_level (RIG * rig, vfo_t vfo, setting_t level, value_t val)
{
	int ret = RIG_OK;
	uint8_t fifi_preamp;
	int16_t fifi_volume;
	uint8_t fifi_squelch;
	uint8_t fifi_agc;


	switch (level) {
		/* Preamplifier (ADC 0/+6dB switch) */
		case RIG_LEVEL_PREAMP:
			/* Value can be 0 (0 dB) or 1 (+6 dB) */
			fifi_preamp = 0;
			if (val.i == 6) {
				fifi_preamp = 1;
			}
			ret = fifisdr_usb_write(rig, REQUEST_FIFISDR_WRITE, 0,
									19,	/* Preamp */
									(char *)&fifi_preamp, sizeof(fifi_preamp));
		break;

		/* RX volume control */
		case RIG_LEVEL_AF:
			/* Transform Hamlib value (float: 0...1) to an integer range (0...100) */
			fifi_volume = (int16_t)(val.f * 100.0f);
			if (fifi_volume < 0) {
				fifi_volume = 0;
			}
			if (fifi_volume > 100) {
				fifi_volume = 100;
			}
			ret = fifisdr_usb_write(rig, REQUEST_FIFISDR_WRITE, 0,
									14,	/* Demodulator volume */
									(char *)&fifi_volume, sizeof(fifi_volume));
		break;

		/* Squelch level */
		case RIG_LEVEL_SQL:
			/* Transform Hamlib value (float: 0...1) to an integer range (0...100) */
			fifi_squelch = (uint8_t)(val.f * 100.0f);
			if (fifi_squelch < 0) {
				fifi_squelch = 0;
			}
			if (fifi_squelch > 100) {
				fifi_squelch = 100;
			}
			ret = fifisdr_usb_write(rig, REQUEST_FIFISDR_WRITE, 0,
									20,	/* Squelch control */
									(char *)&fifi_squelch, sizeof(fifi_squelch));
		break;

		/* AGC */
		case RIG_LEVEL_AGC:
			/* Transform Hamlib enum value to FiFi-SDR selector */
			fifi_agc = 0;
			switch ((enum agc_level_e)val.i) {
				case RIG_AGC_OFF:		fifi_agc = 0;	break;
				case RIG_AGC_SUPERFAST:	fifi_agc = 1;	break;
				case RIG_AGC_FAST:		fifi_agc = 2;	break;
				case RIG_AGC_SLOW:		fifi_agc = 3;	break;
				case RIG_AGC_USER:		fifi_agc = 4;	break;
				case RIG_AGC_MEDIUM:	fifi_agc = 5;	break;
				case RIG_AGC_AUTO:		fifi_agc = 6;	break;
			}
			ret = fifisdr_usb_write(rig, REQUEST_FIFISDR_WRITE, 0,
									21,	/* AGC template */
									(char *)&fifi_agc, sizeof(fifi_agc));
		break;

		/* Unsupported option */
		default:
			ret = -RIG_ENIMPL;
	}

	return ret;
}



static int fifisdr_get_level(RIG *rig, vfo_t vfo, setting_t level, value_t *val)
{
	int ret = RIG_OK;
	uint32_t fifi_meter = 0;
	uint8_t fifi_preamp = 0;
	int16_t fifi_volume = 0;
	uint8_t fifi_squelch = 0;
	uint8_t fifi_agc = 0;


	switch (level) {
		/* Preamplifier (ADC 0/+6dB switch) */
		case RIG_LEVEL_PREAMP:
			ret = fifisdr_usb_read(rig, REQUEST_FIFISDR_READ, 0,
								   19,	/* Preamp */
								   (char *)&fifi_preamp, sizeof(fifi_preamp));
			if (ret == RIG_OK) {
				/* Value can be 0 (0 dB) or 1 (+6 dB) */
				val->i = 0;
				if (fifi_preamp != 0) {
					val->i = 6;
				}
			}
		break;

		/* RX volume control */
		case RIG_LEVEL_AF:
			ret = fifisdr_usb_read(rig, REQUEST_FIFISDR_READ, 0,
								   14,	/* Demodulator volume */
								   (char *)&fifi_volume, sizeof(fifi_volume));
			if (ret == RIG_OK) {
				/* Value is in % (0...100) */
				val->f = 0.0f;
				if ((fifi_volume >=0) && (fifi_volume <= 100)) {
					val->f = (float)fifi_volume / 100.0f;
				}
			}
		break;

		/* Squelch level */
		case RIG_LEVEL_SQL:
			ret = fifisdr_usb_read(rig, REQUEST_FIFISDR_READ, 0,
								   20,	/* Squelch control */
								   (char *)&fifi_squelch, sizeof(fifi_squelch));
			if (ret == RIG_OK) {
				/* Value is in % (0...100) */
				val->f = 0.0f;
				if (fifi_squelch <= 100) {
					val->f = (float)fifi_squelch / 100.0f;
				}
			}
		break;

		/* AGC */
		case RIG_LEVEL_AGC:
			ret = fifisdr_usb_read(rig, REQUEST_FIFISDR_READ, 0,
								   21,	/* AGC template */
								   (char *)&fifi_agc, sizeof(fifi_agc));
			if (ret == RIG_OK) {
				val->i = 0;
				switch (fifi_agc) {
					case 0:	val->i = RIG_AGC_OFF;		break;
					case 1:	val->i = RIG_AGC_SUPERFAST;	break;
					case 2:	val->i = RIG_AGC_FAST;		break;
					case 3:	val->i = RIG_AGC_SLOW;		break;
					case 4:	val->i = RIG_AGC_USER;		break;
					case 5:	val->i = RIG_AGC_MEDIUM;	break;
					case 6:	val->i = RIG_AGC_AUTO;		break;
				}
			}
		break;

		/* Signal strength */
		case RIG_LEVEL_STRENGTH:
			ret = fifisdr_usb_read(rig, REQUEST_FIFISDR_READ, 0,
								   17,	/* S-Meter */
								   (char *)&fifi_meter, sizeof(fifi_meter));
			if (ret == RIG_OK) {
				val->i = fifisdr_fromle32(fifi_meter);
			}
		break;

		/* Unsupported option */
		default:
			ret = -RIG_ENIMPL;
	}

	return ret;
}



static int fifisdr_get_ext_level(RIG *rig, vfo_t vfo, token_t token, value_t *val)
{
	int ret = RIG_OK;
	uint32_t u32;


	switch (token) {
		/* FM center frequency deviation */
		case TOK_LVL_FMCENTER:
			ret = fifisdr_usb_read(rig, REQUEST_FIFISDR_READ, 0,
								   18,	/* FM center frequency */
								   (char *)&u32, sizeof(u32));
			if (ret == RIG_OK) {
				val->f = Hz((int32_t)fifisdr_fromle32(u32));
			}
		break;

		/* Unsupported option */
		default:
			ret = -RIG_ENIMPL;
	}

	return ret;
}

#endif	/* defined(HAVE_LIBUSB) && defined(HAVE_USB_H) */
